'''
Created on 7 okt 2010

@author: Xakt
'''

from settings import *
import math

class AgentBrain():
    
    def __init__(self):
        self.waypoints = [[500, 480], [500, 520], [480, 500], [520, 500]]
    
    def action(self, observation):
        state = self.toState(observation)
        # state = [enemy outer range, enemy inner range, enemy in back, ...
        # ... ammo in front, ammo in back, have ammo, uncontrolled points]
        if state[1] == "1" and state[5] == "1":
            action = self.shootEnemy(observation)
        elif state[0] == "1" and state[5] == "1":
            action = self.turnToEnemy(observation)
        elif state[0] == "1" or state[1] == "1" or state[2] == "1":
            action = self.flee(observation)
        elif state[3] == "1" or state[4] == "1":
            action = self.getAmmo(observation)
        else: # state[6] == 1:
            action = self.goToControlpoint(observation)
            
        if self.friendlyInFront(observation):
            action['speed'] = 10
            action['turn'] = action['turn']+30
        return action
#        else:
#            return self.defend(observation)
    def friendlyInFront(self, observation):
        agents = observation['agents']
        friendlyList = []
        for agent in agents:
            if not self.isEnemy(agent, observation['team']):
                friendlyList.append(agent)
        
        for friendly in friendlyList:
            if self.getDistance(observation['location'], friendly['location']) < MAX_SHOT_DISTANCE:
                angle = self.angleWithAgent(friendly['location'], observation['location'], observation['direction'])
                if angle >= -math.pi/6 and angle <= math.pi/6:
                    return True
        return False
    
    def goToControlpoint(self,observation):
        controlpoints = observation['controlpoints']
        id = observation['id']
        
        ownControlpoint = -1
        if observation['team'] == "Red":
            if observation['location'][0] > 500 and observation['location'][1] > 500:
                ownControlpoint = (550,550)
        else:
            if observation['location'][0] < 500 and observation['location'][1] < 500:
                ownControlpoint = (450,450)
        
        if ownControlpoint == -1:
            if id == 1 or id == 2:
                ownControlpoint = controlpoints[0]['location']
            elif id == 3 or id == 4:
                ownControlpoint = controlpoints[1]['location']
            elif id == 5 or id == 6:
                ownControlpoint = controlpoints[2]['location']
            elif id == 7:
                ownControlpoint = (300, 700)
            elif id == 8:
                ownControlpoint = (700, 300)
            elif id == 9:
                ownControlpoint = (600, 600)
            elif id == 10:
                ownControlpoint = (400, 400)
        
        obstacle = self.obstacleCheck(observation['location'], observation['walls'], ownControlpoint);
        if not obstacle == []:
            # Wall in the way
            ownControlpoint = self.getNearestWaypoint(observation['location'])
        angle = self.angleWithAgent(ownControlpoint, observation['location'], observation['direction'])
        if angle >= -MAX_TURN and angle <= MAX_TURN:
            speed = self.getDistance(observation['location'], ownControlpoint)
        else:
            speed = 0
        action = {
                   'turn': math.radians(angle),
                   'speed': speed,
                   'shoot': False
                  }
        return action 
    
    def isEnemy(self, agent, team):
        if agent['team'] == team:
            return False
        return True
    
    def isControlled(self, controlpoint, team):
        if controlpoint['team'] == team:
            return True
        return False
    
    def angleWithAgent(self, agentlocation, location, direction):
        direction = -direction
        rel_x = agentlocation[0]-location[0]
        rel_y = agentlocation[1]-location[1]
        rot_x = rel_x * math.cos(direction) - rel_y * math.sin(direction)
        rot_y = rel_x * math.sin(direction) + rel_y * math.cos(direction)
        angle = math.atan2(rot_y, rot_x)
        return math.degrees(angle)
    
    def obstacleCheck(self, loc, walls, controlpoint):
        for wall in walls:
            x1 = float(wall['left'])
            y1 = float(wall['top'])
            x2 = float(wall['right'])
            y2 = float(wall['bottom'])
            if controlpoint[0] < loc[0]:
                u1 = float(controlpoint[0])
                v1 = float(controlpoint[1])
                u2 = float(loc[0])
                v2 = float(loc[1])
            else:
                u2 = float(controlpoint[0])
                v2 = float(controlpoint[1])
                u1 = float(loc[0])
                v1 = float(loc[1])
            if x1 - x2 == 0:
                x1 = x1+0.0001
            if u1 - u2 == 0:
                u1 = u1+0.0001
            b1 = (y1 - y2) / (x1 - x2)
            a1 = y1 - (b1*x1)
            if u1 - u2 == 0:
                # Vertical line
                y = a1 + (b1 * u1)
                if y < y1 or y > y2:
                    return []
                else:
                    return wall
            b2 = (v1 - v2) / (u1 - u2)
            a2 = v1 - (b2*u1)
            if b1-b2 == 0:
                return []
            xi = -1* ((a1 - a2) / (b1-b2))
            yi = a1 + (b1*xi) 
            if (x1-xi)*(xi-x2)>=0 and (u1-xi)*(xi-u2)>=0 and (y1-yi)*(yi-y2)>=0 and (v1-yi)*(yi-v2)>=0:
                return wall
        return []
    
    def getNearestWaypoint(self, location):
        dist = 9999
        target = None
        for wp in self.waypoints:
            tmp = self.getDistance(location, wp)
            if tmp < dist:
                dist = tmp
                target = wp
        return target 
    
    def getAmmo(self,observation):
        ammopacks = observation['ammopacks']

        closest_distance = SIGHT*2
        toPickup = []
        for ammopack in ammopacks:
            distance = self.getDistance(observation['location'], ammopack['location'])
            if distance < closest_distance:
                closest_distance = distance
                toPickup = ammopack['location']
        angle = self.angleWithAgent(toPickup, observation['location'], observation['direction'])
        if angle >= -MAX_TURN and angle <= MAX_TURN:
            speed = closest_distance
        else:
            speed = 0
        action = {
                   'turn': math.radians(angle),
                   'speed': speed,
                   'shoot': False
                  }
        return action
    
    def flee(self,observation):
        agents = observation['agents']
        sumAgentLoc = (0,0)
        location = observation['location']
        
        enemyAgents = []
        for agent in agents:
            if self.isEnemy(agent, observation['team']):
                enemyAgents.append(agent)
        
        for agent in enemyAgents:
            relLoc = (agent['location'][0]-location[0],agent['location'][1]-location[1])
            sumAgentLoc = (sumAgentLoc[0]+relLoc[0],sumAgentLoc[1]+relLoc[1])
        sumAgentLoc = (-sumAgentLoc[0]+location[0],-sumAgentLoc[1]+location[1])
        
        action = {
                   'turn': math.radians(self.angleWithAgent(sumAgentLoc, observation['location'], observation['direction'])),
                   'speed': MAX_SPEED,
                   'shoot': False
                  }
        
        return action
    
    def getDistance(self, fromLoc, toLoc):
        distance = math.sqrt(math.pow((toLoc[0] - fromLoc[0]),2) + math.pow((toLoc[1] - fromLoc[1]),2))
        return distance
    
    def turnToEnemy(self, observation):
        agents = observation['agents']
        enemiesInSight = []
        for agent in agents:
            if agent['team'] != observation['team']:
                angle = self.angleWithAgent(agent['location'], observation['location'], observation['direction'])
                if angle >= 2* -MAX_TURN and angle <= 2* MAX_TURN:
                    if MAX_SHOT_DISTANCE < self.getDistance(observation['location'],agent['location']) < SIGHT:
                        enemiesInSight.append(agent)
        
        smallest_angle = MAX_TURN*3
        for enemy in enemiesInSight:
            angle = self.angleWithAgent(enemy['location'],observation['location'], observation['direction'])
            if abs(angle) < abs(smallest_angle):
                smallest_angle = angle
        action = {
                   'turn': math.radians(angle),
                   'speed': MIN_SPEED,
                   'shoot': False
                  }
        return action
    
    def shootEnemy(self,observation):
        agents = observation['agents']
        enemiesInSight = []
        for agent in agents:
            if agent['team'] != observation['team']:
                angle = self.angleWithAgent(agent['location'], observation['location'], observation['direction'])
                if angle >= -MAX_TURN and angle <= MAX_TURN:
                    if self.getDistance(observation['location'],agent['location']) < MAX_SHOT_DISTANCE:
                        enemiesInSight.append(agent)
        
        closest_distance = SIGHT*2
        toAttack = []
        for enemy in enemiesInSight:
            distance = self.getDistance(observation['location'],enemy['location'])
            if distance < closest_distance:
                closest_distance = distance
                toAttack = enemy
        action = {
                   'turn': math.radians(self.angleWithAgent(toAttack['location'], observation['location'], observation['direction'])),
                   'speed': MIN_SPEED,
                   'shoot': True
                  }
        return action        
        
    def toState(self, observation):
        state = [0,0,0,0,0,0,0]
        
        location = observation['location']
        direction = observation['direction']
        agents = observation['agents']
        ammopacks = observation['ammopacks']
        controlpoints = observation['controlpoints']

        # check agents
        if agents != []:
            for agent in agents:
                if self.isEnemy(agent, observation['team']):
                    angle = self.angleWithAgent(agent['location'],location,direction)
                    if angle <= MAX_TURN and angle >= -MAX_TURN and self.getDistance(location, agent['location']) < MAX_SHOT_DISTANCE:
                        state[1] = 1
                    elif angle <= 2*MAX_TURN and angle >= 2* -MAX_TURN and self.getDistance(location, agent['location']) < SIGHT:
                        state[0] = 1
                    else:
                        state[2] = 1
        
        # check ammopacks
        if ammopacks != []:
            for ammopack in ammopacks:
                angle = self.angleWithAgent(ammopack['location'],location,direction)
                if angle <= MAX_TURN and angle >= -MAX_TURN:
                    state[3] = 1
                else:
                    state[4] = 1
                    
        # check ammo
        if(observation['ammo'] > 0):
            state[5] = 1
        
        # check controlpoints
        for controlpoint in controlpoints:
            if not self.isControlled(controlpoint, observation['team']):
                state[6] = 1
        
        # construct string
        state = ''.join(str(n) for n in state)
        return state